#ifndef Array2D_
#define Array2D_

#include <iostream>

#include "AlogException.h"
#include "Array1D.h"

template<class T>
class Array2D {
  public:
    Array2D( int r = 0, int c = 0 );
    Array2D( const Array2D<T>& m );
    ~Array2D() { delete [] row; }

    int Rows() const { return rows; }
    int Columns() const { return cols; }

    Array1D<T>& operator[]( int i ) const;
    Array2D<T>& operator=( const Array2D<T>& m );
    Array2D<T> operator+() const;
    Array2D<T> operator+( const Array2D<T>& m ) const;
    Array2D<T> operator-() const;
    Array2D<T> operator-( const Array2D<T>& m ) const;
    Array2D<T> operator*( const Array2D<T>& m ) const;
    Array2D<T>& operator+=( const T& x );

    void Output( std::ostream& out ) const;

  private:
    int rows, cols;
    Array1D<T> *row;
};



template<class T>
Array2D<T>::Array2D( int r/*= 0 */, int c/*= 0 */ )
{
    if ( r < 0 || c < 0 ) throw BadInitializers();

    if ( (!r || !c) && (r || c) )
        throw BadInitializers();

    rows = r;
    cols = c;

    row = new Array1D<T>[r];

    for ( int i = 0; i < r; i++ )
        row[i].ReSize( c );
}

template<class T>
Array2D<T>::Array2D( const Array2D<T>& m )
{
    rows = m.rows;
    cols = m.cols;

    row = new Array1D<T>[rows];

    for ( int i = 0; i < rows; i++ )
        row[i] = m.row[i];
}

template<class T>
Array2D<T>& Array2D<T>::operator=( const Array2D<T>& m )
{
    if ( this != &m ) {
        delete [] row;
        rows = m.rows;
        cols = m.cols;
        row = new Array1D<T>[rows];

        for ( int i = 0; i < rows; i++ )
            row[i] = m.row[i];
    }

    return *this;
}

template<class T>
Array1D<T>& Array2D<T>::operator[]( int i ) const
{
    if ( i < 0 || i >= rows ) throw OutOfBounds();

    return row[i];
}

template<class T>
Array2D<T> Array2D<T>::operator+( const Array2D<T>& m ) const
{
    if ( rows != m.rows || cols != m.cols )
        throw SizeMismatch();

    Array2D<T> w( rows, cols );

    for ( int i = 0; i < rows; i++ )
        w.row[i] = row[i] + m.row[i];

    return w;
}

template<class T>
Array2D<T> Array2D<T>::operator-(const Array2D<T>& m) const
{
    if ( rows != m.rows || cols != m.cols )
        throw SizeMismatch();

    Array2D<T> w( rows, cols );

    for ( int i = 0; i < rows; i++ )
        w.row[i] = row[i] - m.row[i];

    return w;
}

template<class T>
Array2D<T> Array2D<T>::operator-() const
{
    Array2D<T> w( rows, cols );

    for ( int i = 0; i < rows; i++ )
        w.row[i] = -row[i];

    return w;
}

template<class T>
Array2D<T> Array2D<T>::operator*( const Array2D<T>& m ) const
{
    if ( cols != m.rows ) throw SizeMismatch();

    Array2D<T> w( rows, m.cols );

    for ( int i = 0; i < rows; i++ )
        for ( int j = 0; j < m.cols; j++ ) {
            T sum = (*this)[i][0] * m[0][j];

            for ( int k = 1; k < cols; k++ )
                sum += (*this)[i][k] * m[k][j];

            w[i][j] = sum;
      }

    return w;
}

template<class T>
Array2D<T>& Array2D<T>::operator+=( const T& x )
{
    for ( int i = 0; i < rows; i++ )
        row[i] += x;

    return *this;
}

template<class T>
void Array2D<T>::Output( std::ostream& out ) const
{
    for ( int i = 0; i < rows; i++ ) {
        out << row[i] << std::endl;
    }
}

template<class T>
std::ostream& operator<<( std::ostream& out, const Array2D<T>& x )
{
    x.Output( out );

    return out;
}

#endif

